﻿namespace UnityEditor.Controls.HierarchyControl
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.IO;
    using System.Linq.Expressions;
    using System.Windows;
    using System.Windows.Controls;
    using UnityEditor.Controls.HierarchyControl.ViewModel;
    using UnityEngine.Core;
    using System.Windows.Input;
    using UnityEditor.Layout;
    using UnityEditor.Controls.ProjectControl.ViewModel;
    using UnityEngine.Components;

    /// <summary>
    /// Interaction logic for FolderPicker.xaml
    /// </summary>
    public partial class HierarchyControl : UserControl, INotifyPropertyChanged
    {
        private const string EmptyItemName = "Empty";

        private const string NewFolderName = "New Folder";

        private const int MaxNewFolderSuffix = 10000;

        private HierarchyItem root;

        private HierarchyItem selectedItem;

        private string selectedPath = string.Empty;

        public HierarchyControl()
        {
            this.InitializeComponent();

            this.Init();
        }

        #region drag & drop
        private Point startPoint = new Point();
        private bool IsDragging
        {
            get;
            set;
        }

        private void TreeView_DragOver(object sender, DragEventArgs e)
        {
            DragOperation(false, sender, e);
        }

        private void TreeView_DragEnter(object sender, DragEventArgs e)
        {
            DragOperation(false, sender, e);
        }

        private void TreeView_Drop(object sender, DragEventArgs e)
        {
            DragOperation(true, sender, e);
        }

        private void DragOperation(bool bDrop, object sender, DragEventArgs e)
        {
            try
            {
                if (e.Data.GetData("HierarchyItem") != null)
                {
                    //var s = e.Data.GetData("HierarchyItem") as FrameworkElement;
                    HierarchyItem source = (e.Data.GetData("HierarchyItem") as FrameworkElement).DataContext as HierarchyItem;
                    HierarchyItem target = (e.OriginalSource as FrameworkElement).DataContext as HierarchyItem;

                    if (source.IsAncestorOf(target) || source == target)
                    {
                        e.Handled = true;
                        e.Effects = DragDropEffects.Move;
                    }
                    else
                    {
                        if (bDrop)
                        {
                            if (source != null && source.Parent != null)
                                source.Parent.RemoveChild(source);
                            target.AddChild(source);
                        }
                        e.Handled = true;
                        e.Effects = DragDropEffects.Move;
                    }
                }
                if (e.Data.GetData("ProjectItem") != null)
                {
                    ProjectItem source = (e.Data.GetData("ProjectItem") as FrameworkElement).DataContext as ProjectItem;
                    HierarchyItem target = (e.OriginalSource as FrameworkElement).DataContext as HierarchyItem;

                    string fileName = Path.GetFileName(source.FullPath);
                    string fileNameWithoutExtention = Path.GetFileNameWithoutExtension(source.FullPath);
                    string k = Path.GetExtension(fileName);
                    if (Path.GetExtension(fileName) == ".cs" && target.GameObject.IsAvailableComponent(fileNameWithoutExtention))
                    {
                        if (bDrop)
                        {
                            target.GameObject.AddComponent(fileNameWithoutExtention);
                            if (target.GameObject == Workspace.Instance.GameObject)
                                Workspace.Instance.GameObject = Workspace.Instance.GameObject;
                        }
                        e.Handled = true;
                        e.Effects = DragDropEffects.Move;
                    }
                    else
                    {
                        if (Path.GetExtension(fileName) == ".mdx")
                        {
                            if (bDrop)
                            {
                                target.GameObject.RemoveComponent("Renderer");
                                Renderer r = target.GameObject.AddComponent("Renderer");
                                r.ModelPath = source.FullPath;
                                if (target.GameObject == Workspace.Instance.GameObject)
                                    Workspace.Instance.GameObject = Workspace.Instance.GameObject;
                            }
                            e.Handled = true;
                            e.Effects = DragDropEffects.Move;
                        }
                        else
                        {
                            e.Handled = true;
                            e.Effects = DragDropEffects.None;
                        }
                    }
                }
            }
            catch (System.Exception ex)
            {
                e.Handled = true;
                e.Effects = DragDropEffects.None;
            }
        }

        private void TreeView_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (IsDragging == false)
                startPoint = e.GetPosition(null);
        }

        private void TreeView_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed && !IsDragging)
            {
                Point position = e.GetPosition(null);
                if (Math.Abs(position.X - startPoint.X) > SystemParameters.MinimumHorizontalDragDistance ||
                    Math.Abs(position.Y - startPoint.Y) > SystemParameters.MinimumVerticalDragDistance)
                {
                    IsDragging = true;
                    DataObject data = new DataObject();
                    data.SetData("HierarchyItem", e.OriginalSource);
                    DragDropEffects de = DragDrop.DoDragDrop(this.TreeView, data, DragDropEffects.Move);
                    IsDragging = false;
                }
            }
        }
        #endregion

        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Get/set root item of tree view
        /// </summary>
        public HierarchyItem Root
        {
            get
            {
                return this.root;
            }

            private set
            {
                // for some (unknown) reason this cannot be guarded with an if != value block ???
                this.root = value;
                this.NotifyPropertyChanged(() => this.Root);
            }
        }

        /// <summary>
        /// Get/set currently selected item in the tree view
        /// </summary>
        public HierarchyItem SelectedItem
        {
            get
            {
                return this.selectedItem;
            }

            private set
            {
                if (this.selectedItem != value)
                {
                    this.selectedItem = value;
                    if (selectedItem != null)
                        Workspace.Instance.GameObject = this.selectedItem.GameObject;
                    else
                        Workspace.Instance.GameObject = null;
                    this.NotifyPropertyChanged(() => this.SelectedItem);
                }
            }
        }

        /// <summary>
        /// Get/set path of the currently selected element in the treeview
        /// </summary>
        public string SelectedPath
        {
            get
            {
                return this.selectedPath;
            }

            // Setter is called by TreeView_SelectedItem event ONLY
            // It is currently not clear how to sync back new paths to the tree view - although its possible through the
            // UpdateInitialPathUI method but the textbox looses its focus if that method is used - so a method without loosing
            // focus would be preferable ...
            private set
            {
                if (value != this.selectedPath)
                {
                    this.selectedPath = value;

                    // Sync back to treeView Problem: TreeView acquires focus and textbox cannot be used for input
                    ////if (System.IO.Directory.Exists(this.selectedPath))
                    //// if (this.SelectedItem != null)
                    ////if (this.selectedPath != this.SelectedItem.GetFullPath())
                    //// this.InitialPath = this.selectedPath;
                    ////}
                    //// else
                    ////{
                    //// this.InitialPath = this.selectedPath;
                    ////}
                    this.NotifyPropertyChanged(() => this.SelectedPath);
                }
                //// this.txtPath.Text = value;  // DBa set path into path text box
            }
        }

        public void CreateNewFolder()
        {
            this.CreateNewFolderImpl(this.SelectedItem);
        }

        public void RefreshTree()
        {
            this.Root = null;

            this.Init();
        }

        #region INotifyPropertyChanged Members
        public void NotifyPropertyChanged<TProperty>(Expression<Func<TProperty>> property)
        {
            var lambda = (LambdaExpression)property;
            MemberExpression memberExpression;
            if (lambda.Body is UnaryExpression)
            {
                var unaryExpression = (UnaryExpression)lambda.Body;
                memberExpression = (MemberExpression)unaryExpression.Operand;
            }
            else
                memberExpression = (MemberExpression)lambda.Body;

            this.OnPropertyChanged(memberExpression.Member.Name);
        }

        private void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private void Init()
        {
            this.root = new HierarchyItem(null, Workspace.Instance.GameScene);
            DataContext = this.root;
            this.Root = this.root; // to notify UI

            Workspace.Instance.GameSceneChanged += new EventHandler((o, e) =>
            {
                this.root = new HierarchyItem(null, Workspace.Instance.GameScene);
                this.DataContext = this.root;
                this.Root = this.root;
            });
        }

        private void Refresh_Click(object sender, RoutedEventArgs e)
        {
            this.Init();
        }

        private void TreeView_Selected(object sender, RoutedEventArgs e)
        {
            var tvi = e.OriginalSource as TreeViewItem;

            if (tvi != null)
                this.SelectedItem = tvi.DataContext as HierarchyItem;
        }

        private void TreeView_Expanded(object sender, RoutedEventArgs e)
        {
            var tvi = e.OriginalSource as TreeViewItem;
            var treeItem = tvi.DataContext as HierarchyItem;

            if (treeItem != null)
            {
                treeItem.TryLoadChildren(2);
            }
            else
                throw new Exception();
        }

        /// <summary>
        /// Build a stack (queue) of directory entries from root to subDir and return it
        /// </summary>
        /// <param name="subDir"></param>
        /// <returns></returns>
        private Stack<DirectoryInfo> TraverseUpToRoot(DirectoryInfo subDir)
        {
            if (subDir == null)
                return null;

            if (!subDir.Exists)
                return null;

            Stack<DirectoryInfo> queue = new Stack<DirectoryInfo>();
            queue.Push(subDir);
            DirectoryInfo ti = subDir.Parent;

            while (ti != null)
            {
                queue.Push(ti);
                ti = ti.Parent;
            }

            return queue;
        }

        private void CreateNewFolderImpl(HierarchyItem parent)
        {
            // try
            //{
            // if (parent == null)
            //        return;

            // var parentPath = parent.FullPath;
            //    var newDirName = this.GenerateNewFolderName(parentPath);
            // var newPath = Path.Combine(parentPath, newDirName);
            //    Directory.CreateDirectory(newPath);

            // var childs = parent.Children;
            //    var newChild = new HierarchyItem(newPath, parent);
            // childs.Add(newChild);
            //    parent.Children = childs.OrderBy(c => c.Name).ToObservableCollection();

            // parent.IsExpanded = true;
            //}
            // catch (Exception ex)
            //{
            // MessageBox.Show(string.Format("Can't create new folder. Error: {0}", ex.Message));
            //}
        }

        private string GenerateNewFolderName(string parentPath)
        {
            string result = NewFolderName;

            if (Directory.Exists(Path.Combine(parentPath, result)))
            {
                for (int i = 1; i < MaxNewFolderSuffix; ++i)
                {
                    var nameWithIndex = string.Format(NewFolderName + " {0}", i);

                    if (!Directory.Exists(Path.Combine(parentPath, nameWithIndex)))
                    {
                        result = nameWithIndex;
                        break;
                    }
                }
            }

            return result;
        }

        private void CreateMenuItem_Click(object sender, RoutedEventArgs e)
        {
            var item = sender as MenuItem;
            if (item != null)
            {
                var context = item.DataContext as HierarchyItem;
                if (context != null)
                    context.AddChild(new HierarchyItem(context, new GameObject()));
            }
        }

        private void DeleteMenuItem_Click(object sender, RoutedEventArgs e)
        {
            var item = sender as MenuItem;
            if (item != null)
            {
                var context = item.DataContext as HierarchyItem;
                if (context != null && context.Parent != null)
                    context.Parent.RemoveChild(context);
            }
        }

        private void RenameMenuItem_Click(object sender, RoutedEventArgs e)
        {
            var item = sender as MenuItem;
            if (item != null)
            {
                var context = item.DataContext as HierarchyItem;
                context.IsEditing = true;

            }
        }
        #endregion

        private void EnterKeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                HierarchyItem p = (sender as TextBox).DataContext as HierarchyItem;
                if (p != null)
                    p.IsEditing = false;
            }
        }
    }
}
